package cmd

import (
	"bytes"
	"fmt"
	"html/template"
	"io"
	"os"
	"path/filepath"
	"strings"
	"xorms/funs"

	"github.com/gookit/config/v2"
	"github.com/gookit/config/v2/json"
	"github.com/gookit/config/v2/toml"
	"github.com/gookit/config/v2/yaml"
	"github.com/schollz/progressbar/v3"
	"github.com/spf13/cobra"
	"xorm.io/xorm"
)

var defaultCfgPath string
var rootPath string

var (
	RootCmd = &cobra.Command{
		Use: "xorms",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Printf("xxx:%+v", defaultCfgPath)
			ext := filepath.Ext(defaultCfgPath)
			if ext == ".yml" || ext == ".yaml" {
				config.AddDriver(yaml.Driver)
			} else if ext == ".json" {
				config.AddDriver(json.Driver)
			} else if ext == ".ini" {
				config.AddDriver(toml.Driver)
			}

			config.WithOptions(func(opt *config.Options) {
				opt.DecoderConfig.TagName = "config"
			})
			err := config.LoadFiles(defaultCfgPath)

			if err != nil {
				panic(err)
			}

			var defCfg funs.Config
			if err = config.Decode(&defCfg); err != nil {
				panic(err)
			}
			if err = config.Decode(&defCfg.Filed); err != nil {
				panic(err)
			}
			funs.GlobalFixedFieldOld.Set(defCfg.Filed)
			if err = run(defCfg); err != nil {
				panic(err)
			}
		},
	}
)

func init() {
	RootCmd.Flags().StringVarP(&rootPath, "root", "r", "", "yml file to apply for reverse")
	RootCmd.Flags().StringVarP(&defaultCfgPath, "file", "f", "./default.yml", "yml file to apply for reverse")
}

func run(cfg funs.Config) (err error) {
	if len(rootPath) != 0 {
		cfg.RootPath = rootPath
	} else if len(cfg.RootPath) == 0 {
		cfg.RootPath = "./"
	}
	if len(cfg.CfgPath) == 0 {
		cfg.CfgPath = "configs"
	}
	if len(cfg.OutPath) == 0 {
		cfg.OutPath = "models"
	}
	cfgPath := funs.Path(cfg.RootPath, cfg.CfgPath)
	if err = os.MkdirAll(cfgPath, os.ModePerm); err != nil {
		return
	}

	outPath := funs.Path(cfg.RootPath, cfg.OutPath)

	if err = os.MkdirAll(outPath, os.ModePerm); err != nil {
		return
	}
	if cfg.IsClean {
		dfls, errs := os.ReadDir(outPath)
		if errs != nil {
			return errs
		}
		for i := range dfls {
			if !strings.HasSuffix(dfls[i].Name(), ".go") {
				continue
			}
			if err = os.Remove(outPath + "/" + dfls[i].Name()); err != nil {
				return
			}
		}
	}
	mapper := funs.GetMapperByName("")
	for i := range cfg.FieldColumns {
		funs.GlobalFieldColumnMap[cfg.FieldColumns[i].ColumnName] = cfg.FieldColumns[i]
	}
	funs.GlobalColumnTypesMap = cfg.ColumnTypes
	for i := range cfg.ImporatPaths {
		if len(cfg.ImporatPaths[i].Path) == 0 {
			continue
		}
		name := cfg.ImporatPaths[i].Name
		if len(name) == 0 {
			name = filepath.Base(cfg.ImporatPaths[i].Path)
		}
		funs.GlobalImpoartPathMap[name] = cfg.ImporatPaths[i]
	}
	for tyName, cloNames := range cfg.ColumnNameTypes {
		for i := range cloNames {
			funs.GlobalColumnNameTypesMap[cloNames[i]] = tyName
		}
	}
	funcsTemp := template.FuncMap{
		"TableMapper":  mapper.Table2Obj,
		"ColumnMapper": mapper.Table2Obj,
		"Tag":          funs.Tag,
	}
	orm, err := xorm.NewEngine(cfg.DbType, cfg.DbConn)
	if err != nil {
		return err
	}
	tables, err := orm.DBMetas()
	if err != nil {
		return err
	}
	tables = funs.FilterTables(tables, cfg.ExcludeTables)

	colTypeMap := funs.NewMap[string, map[string]string](funs.GlobalColumnTypesMap, true, func(s string) string {
		return s
	})
	jsonMap := funs.NewMap[funs.FieldColumn, map[string]funs.FieldColumn](funs.GlobalFieldColumnMap, false, func(fc funs.FieldColumn) string {
		return fc.JsonName
	})
	importMap := funs.NewMap[funs.ImporatPath, map[string]funs.ImporatPath](funs.GlobalImpoartPathMap, false, func(fc funs.ImporatPath) string {
		return fc.Path
	})
	columnNameTypeMap := funs.NewMap[string, map[string]string](funs.GlobalColumnNameTypesMap, false, func(fc string) string {
		return fc
	})
	jsonmapper := funs.GetMapperByName("")
	if !cfg.JsonNameRule {
		jsonmapper = funs.GetMapperByName("same")
	}
	bar := progressbar.Default(int64(len(tables)), "Writing file...")
	for _, table := range tables {
		ympaht, _ := funs.IsFileYml(cfgPath, table.Name)
		newColumnNameTypesMap := map[string]string{}
		newImportMap := make(map[string]funs.ImporatPath)
		newColumnTypesMap := map[string]string{}
		newFieldColumnMap := map[string]funs.FieldColumn{}
		if len(ympaht) != 0 {
			coBJ := config.New(table.Name)
			ext := filepath.Ext(ympaht)
			if ext == ".yml" || ext == ".yaml" {
				coBJ.AddDriver(yaml.Driver)
			} else if ext == ".json" {
				coBJ.AddDriver(json.Driver)
			} else if ext == ".ini" {
				coBJ.AddDriver(toml.Driver)
			}
			coBJ.WithOptions(func(opt *config.Options) {
				opt.DecoderConfig.TagName = "config"
			})
			err := coBJ.LoadFiles(ympaht)
			if err != nil {
				panic(err)
			}
			var tableCfg funs.Model
			if err = coBJ.Decode(&tableCfg); err != nil {
				panic(err)
			}
			if err = coBJ.Decode(&tableCfg.Filed); err != nil {
				panic(err)
			}
			funs.GlobalFixedFieldOld.Set(tableCfg.Filed)
			coBJ.ClearAll()
			for i := range tableCfg.FieldColumns {
				newFieldColumnMap[tableCfg.FieldColumns[i].ColumnName] = tableCfg.FieldColumns[i]
			}
			newColumnTypesMap = tableCfg.ColumnTypes
			for i := range tableCfg.ImporatPaths {
				if len(tableCfg.ImporatPaths[i].Path) == 0 {
					continue
				}
				name := tableCfg.ImporatPaths[i].Name
				if len(name) == 0 {
					name = filepath.Base(tableCfg.ImporatPaths[i].Path)
				}
				newImportMap[name] = tableCfg.ImporatPaths[i]
			}
			for tyName, cloNames := range tableCfg.ColumnNameTypes {
				for i := range cloNames {
					newColumnNameTypesMap[cloNames[i]] = tyName
				}
			}
		}

		jsonMap.ResetAdd(newFieldColumnMap, func(fc funs.FieldColumn) string {
			return fc.JsonName
		})
		colTypeMap.ResetAdd(newColumnTypesMap, func(s string) string {
			return s
		})

		importMap.ResetAdd(newImportMap, func(s funs.ImporatPath) string {
			return s.Path
		})

		columnNameTypeMap.ResetAdd(newColumnNameTypesMap, func(s string) string {
			return s
		})

		imports := funs.Import[string, map[string]string](colTypeMap, table, columnNameTypeMap.Map(), importMap.Map())
		t := template.New("xorms")
		t.Funcs(funcsTemp)
		t.Funcs(template.FuncMap{
			"Type": funs.Type[string, map[string]string](colTypeMap, columnNameTypeMap.Map()),
			"Json": funs.Json[funs.FieldColumn, map[string]funs.FieldColumn](jsonMap, jsonmapper),
		})
		tmpl, err := t.Parse(string(funs.DefaultGolangTemplateTable))
		if err != nil {
			return err
		}

		fl, err := os.Create(outPath + "/" + table.Name + ".go")
		if err != nil {
			return err
		}

		newbytes := bytes.NewBufferString("")
		err = tmpl.Execute(newbytes, map[string]interface{}{
			"Table":   table,
			"Imports": imports,
		})
		if err != nil {
			return err
		}
		tplcontent, err := io.ReadAll(newbytes)
		if err != nil {
			return err
		}
		source, err := funs.FoarmatGo(string(tplcontent))
		if err != nil {
			fl.Write(tplcontent)
		} else {
			fl.Write(source)
		}
		fl.Close()
		bar.Add(1)
	}
	return
}
